package ar.edu.utn.frba.tacs.warnes.entities;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Transient;

import org.apache.log4j.Logger;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;

import ar.edu.utn.frba.tacs.warnes.aop.Utils;
import ar.edu.utn.frba.tacs.warnes.dao.BaseDaoImpl;
import ar.edu.utn.frba.tacs.warnes.dao.PiezaDao;
import ar.edu.utn.frba.tacs.warnes.excepciones.NoPuedoVenderPiezaDisponibleException;
import ar.edu.utn.frba.tacs.warnes.excepciones.YaEstaReservadaException;
import ar.edu.utn.frba.tacs.warnes.excepciones.YaEstaVendidaException;
import ar.edu.utn.frba.tacs.warnes.pieza.PiezaDisponibleState;
import ar.edu.utn.frba.tacs.warnes.pieza.PiezaReservadaState;
import ar.edu.utn.frba.tacs.warnes.pieza.PiezaState;
import ar.edu.utn.frba.tacs.warnes.pieza.PiezaVendidaState;
import ar.edu.utn.frba.tacs.warnes.utils.ContenedorWarnes;
import ar.edu.utn.frba.tacs.warnes.utils.ConstantesWarnes.Categoria;

@Entity
public class Pieza extends BaseEntityImpl {
	@Transient
	Logger log = Logger.getLogger(Pieza.class);

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO, generator = "PiezaSeq")
	@SequenceGenerator(name = "PiezaSeq", sequenceName = "PiezaSeq")
	private long id;

	// TODO como mapeo un state en la base de datos??? :S
	@Transient
	private PiezaState piezaState;
	private Categoria categoria;
	private String descripcion;

	private Float precioEnPesos;

	@ManyToOne(fetch = FetchType.LAZY)
	@Cascade(value = { CascadeType.ALL })
	private Auto autoOrigen;

	// TODO esta feo tener 2 campos excluyentes.
	// osea, la pieza esta en una venta O en una reserva
	@ManyToOne(fetch = FetchType.LAZY)
	@Cascade(value = { CascadeType.ALL })
	private Venta venta;

	@OneToOne(fetch = FetchType.LAZY)
	@Cascade(value = { CascadeType.ALL })
	private Reserva reserva;

	public Pieza(String descripcion) {
		this.setDescripcion(descripcion);
		this.log.debug("Se ha CREADO una nueva PIEZA llamada "+descripcion+".");
	}

	public Pieza() {
		this.log.debug("Se ha CREADO una nueva PIEZA.");
	}

	public long getId() {
		return this.id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public Venta getVenta() {
		return this.venta;
	}

	public void setVenta(Venta venta) {
		this.venta = venta;
	}

	public Reserva getReserva() {
		return this.reserva;
	}

	public void setReserva(Reserva reserva) {
		this.reserva = reserva;
	}

	public Categoria getCategoria() {
		return this.categoria;
	}

	public void setCategoria(Categoria partes) {
		this.categoria = partes;
	}

	public String getDescripcion() {
		return this.descripcion;
	}

	public void setDescripcion(String descripcion) {
		this.descripcion = descripcion;
	}

	/**
	 * Setea el auto a la pieza y agrega la pieza a la lista de piezas del auto.
	 * 
	 * @param autoOrigen
	 */
	public void setAutoOrigen(Auto autoOrigen) {
		if (this.getAutoOrigen() != null) {
			this.getAutoOrigen().borrarPieza(this);
		}
		if (autoOrigen != null) {
			this.autoOrigen = autoOrigen;
			autoOrigen.agregarPieza(this);
		}
	}

	public Auto getAutoOrigen() {
		return this.autoOrigen;
	}

	/**
	 * @return <b>true</b> si la Pieza esta disponible
	 */
	public Boolean estaDisponible() {
		return this.getPiezaState().esDisponible();
	}

	/**
	 * @return <b>true</b> si la Pieza esta reservada
	 */
	public Boolean estaReservada() {
		return this.getPiezaState().esReservada();
	}

	/**
	 * @return <b>true</b> si la Pieza esta vendida
	 */
	public Boolean estaVendida() {
		return this.getPiezaState().esVendida();
	}

	/**
	 * Cambia el estado de la pieza <b><Disponible, Reservada o Vendida></b>
	 * 
	 * @param state
	 */
	public void cambiarEstado(PiezaState state) {
		this.setPiezaState(state);
	}

	private void setPiezaState(PiezaState state) {
		this.piezaState = state;
	}

	public PiezaState getPiezaState() {
		return this.piezaState;
	}

	/**
	 * La pieza se agrego a una reserva por lo tanto cambia su estado a
	 * Reservado
	 */
	public void reservate(Reserva reserva) {
		this.cambiarEstado(PiezaReservadaState.getInstance());
		this.setReserva(reserva);
		this.setVenta(null);
	}

	/**
	 * Solo se puede reservar una pieza diponible.
	 */
	public void puedoReservarte() {
		if (this.estaReservada()) {
			throw new YaEstaReservadaException(this);
		} else if (this.estaVendida()) {
			throw new YaEstaVendidaException(this);
		}
	}

	/**
	 * Solo se puede vender una pieza reservada.
	 * 
	 * @throws YaEstaVendidaException
	 */
	public void puedoVenderte() {
		if (this.estaDisponible()) {
			throw new NoPuedoVenderPiezaDisponibleException(this);
		} else if (this.estaVendida()) {
			throw new YaEstaVendidaException(this);
		}
	}

	public void vendete(Venta venta) {
		this.cambiarEstado(PiezaVendidaState.getInstance());
		this.setVenta(venta);
		this.setReserva(null);
	}

	public void setPrecioEnPesos(Float precioEnPesos) {
		this.precioEnPesos = precioEnPesos;
	}

	/**
	 * El precio en pesos de la pieza se multiplica por un indice del
	 * contenedor. Ej: Si estoy trabajando en dolares, el indice sera (1/3,85) y
	 * al multiplicarse por el precio de la pieza, devolvera el valor de la
	 * misma en dolares (Por ahora no tiene sentido tener un objeto Precio.)
	 * 
	 * @return
	 */
	public Float getPrecioSegunMoneda() {
		return this.getPrecioEnPesos() / ContenedorWarnes.getInstance().getValorMoneda();
	}

	public Float getPrecioEnPesos() {
		return this.precioEnPesos == null ? 0f : this.precioEnPesos;
	}

	public void disponibilizate() {
		this.cambiarEstado(PiezaDisponibleState.getInstance());
		this.setVenta(null);
		this.setReserva(null);
	}

	public void save() {
		BaseDaoImpl<Pieza> dao = PiezaDao.getInstance();
		dao.save(this);
	}

	public void delete() {
		BaseDaoImpl<Pieza> dao = PiezaDao.getInstance();
		dao.delete(this);
	}

	public Float getPrecioParseado() {
		return Utils.parseFloatTo2DecimalesHalfUp(this.getPrecioSegunMoneda());
	}

}
